﻿module Casanova.Game

open Casanova.Coroutines
open Casanova.Core
open Casanova.Utilities
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Content
open Microsoft.Xna.Framework.Graphics
open Casanova.Drawing

let private use_async = false

/// Create a Casanova game; the start_game function initializes the world, the main script, and the input scripts; resolution_x and resolution_y are the requested 
/// screen resolution, and fullscreen determines whether or not to initialize the game window in fullscreen. Invoke the Run method to launch the game after its
/// construction.
type Game<'world>(start_game : GraphicsDevice * ContentManager * (Unit -> Unit) * SpriteLayer -> 'world * Coroutines.Coroutine<Unit> * List<Coroutines.Coroutine<Unit>>, 
                  resolution_x, resolution_y, fullscreen, name, ai_tick_interval : float32<s>, input_tick_interval : float32<s>) as this =
  [<DefaultValue>]
  val mutable graphics : GraphicsDeviceManager
  [<DefaultValue>]
  val mutable draw_world : Unit -> Unit
  [<DefaultValue>]
  val mutable update_world : float32 -> Unit
  [<DefaultValue>]
  val mutable update_and_draw_world_async : float32 -> Unit
  [<DefaultValue>]
  val mutable default_layer : SpriteLayer

  inherit Microsoft.Xna.Framework.Game()
    do this.graphics <- new GraphicsDeviceManager(this)
    do this.IsFixedTimeStep <- false
    do this.graphics.SynchronizeWithVerticalRetrace <- false
  //    do this.graphics.PreferMultiSampling <- true
    do this.graphics.PreferredBackBufferWidth <- resolution_x
    do this.graphics.PreferredBackBufferHeight <- resolution_y
    do this.graphics.IsFullScreen <- fullscreen
    do this.IsMouseVisible <- true
    do this.Window.Title <- name
    do this.Content.RootDirectory <- "Content"

    /// Initialize the game by invoking start_game, storing the world, main script and input scripts
    /// as the current values, and by creating (via cached reflection) the draw and update functions 
    /// based on the shape of the 'world type.
    override game.Initialize() =
//      let form = System.Windows.Forms.Control.FromHandle(this.Window.Handle) 
//      let form = form :?> System.Windows.Forms.Form
//      do form.Location <- new System.Drawing.Point(0, 0)      
      this.default_layer <- SpriteLayer.Create(this.GraphicsDevice, this.Content, BlendState.AlphaBlend)
      let (world,main, input) = start_game(this.GraphicsDevice, this.Content, this.Exit, this.default_layer)
      do commit_rule_updates()
      let input = [ for i in input do yield repeat_ i ]
      for i in input do run_input_script i
      let main = var main
//      do this.update_world <- SlowStateTraversal.update_world world main
//      do this.draw_world   <- SlowStateTraversal.draw_world this.default_layer world
      do this.update_world <- CachedStateTraversal.update_world (ai_tick_interval |> float32) (input_tick_interval |> float32) world main
      do this.draw_world   <- CachedStateTraversal.draw_world this.default_layer world

      do clear_scripts()

//      do this.update_and_draw_world_async <- CachedStateTraversal.update_and_draw_world_async world this.default_layer main 
      do base.Initialize()
    
    /// Update the game world by invoking the generated update function once.
    override game.Update(gameTime) =
      if use_async |> not then
        let dt = gameTime.ElapsedGameTime.TotalSeconds |> float32 |> min 0.2f
        let dt = dt * time_speed
        do this.update_world dt
      do base.Update gameTime

    /// Draw the game world by invoking the generated draw function once.
    override game.Draw(gameTime) =
      do this.GraphicsDevice.Clear(Color.Black)
      if use_async |> not then
        do this.draw_world ()
      else
        let dt = gameTime.ElapsedGameTime.TotalSeconds |> float32 |> min 0.2f
        let dt = dt * time_speed
        do this.update_and_draw_world_async dt
      do base.Draw gameTime


    static member Create(start_game : GraphicsDevice * ContentManager * (Unit -> Unit) * SpriteLayer -> 'world * Coroutines.Coroutine<Unit> * List<Coroutines.Coroutine<Unit>>, 
                         resolution_x, resolution_y, fullscreen, name, ai_tick_interval, input_tick_interval) =
        new Game<'world>(start_game, resolution_x, resolution_y, fullscreen, name, ai_tick_interval, input_tick_interval)

    static member Create(start_game : GraphicsDevice * ContentManager * (Unit -> Unit) -> 'world * Coroutines.Coroutine<Unit> * List<Coroutines.Coroutine<Unit>>, 
                         ?resolution_x, ?resolution_y, ?fullscreen, ?name, ?ai_tick_interval, ?input_tick_interval ) =
        let resolution_x = defaultArg resolution_x 1024
        let resolution_y = defaultArg resolution_y 768
        let fullscreen = defaultArg fullscreen false
        let name = defaultArg name "Zhe City"
        let ai_tick_interval = defaultArg ai_tick_interval 0.2f<s>
        let input_tick_interval= defaultArg input_tick_interval 0.05f<s>
        let start_game(dev,con,exit,def) = start_game(dev,con,exit)
        Game<'world>.Create(start_game, resolution_x, resolution_y, fullscreen, name, ai_tick_interval, input_tick_interval)
